use syn::{GenericArgument, Ident, PathArguments};
use quote::{format_ident, quote};
use syn::{Type, Error};
use crate::utils;

pub fn generate_path_args_transform_code(params: &[(String, Type)]) -> Vec<proc_macro2::TokenStream> {
    params.iter().map(|param| generate_uri_arg_transform_code(param, format_ident!("path_args"))).collect()
}

pub fn generate_query_args_transform_code(query_params: &[(String, Type)]) -> Vec<proc_macro2::TokenStream> {
    query_params.iter().map(|param| generate_uri_arg_transform_code(param, format_ident!("query_args"))).collect()
}

pub fn generate_body_args_transform_code(body_parm: Option<&(String, Type)>) -> proc_macro2::TokenStream {
    let aqua = utils::get_aqua_path();

    match body_parm {
        Some((param_name, param_type)) => {
            let param_name_ident = format_ident!("{}", param_name);
            quote! {
                let #param_name_ident = match <#param_type as #aqua::convert::FromBody>::from_body(body.unwrap()) {
                    Ok(v) => v,
                    Err(err) => return #aqua::server::ServerRouteError::InvalidBody(
                        format!("Invalid format for argument '{}': {}", #param_name, err)
                    ).into_response(),
                };
            }
        }
        None => quote! {}
    }
}

pub fn generate_state_args_transform_code(state_parm: Option<&(String, Type)>) -> proc_macro2::TokenStream {
    let aqua = utils::get_aqua_path();

    match state_parm {
        Some((param_name, param_type)) => {
            match extract_state_inner_type(param_type) {
                Some(target_ty) => {
                    let state_name_ident = format_ident!("{}", param_name);
                    quote! {
                        let #state_name_ident = match states.get::<#target_ty>() {
                            Some(v) => v,
                            None => return #aqua::server::ServerRouteError::FailedHnalde(
                                format!("No exit state '{}'", #param_name)
                            ).into_response(),
                        };
                    }
                }   
                None => Error::new_spanned(
                    param_type,
                    "Expected state parameter of the form `HttpState<T>`"
                )
                .to_compile_error()
            }
        }
        None => quote! {}
    }
}


fn generate_uri_arg_transform_code(param: &(String, Type), arg_map: Ident) -> proc_macro2::TokenStream {
    let aqua = utils::get_aqua_path();

    let name_str = &param.0;
    let name_ident = format_ident!("{}", name_str);
    let ty = &param.1;

    // Is &str or String
    let is_str = match ty {
        Type::Reference(r) => {
            if let Type::Path(p) = &*r.elem {
                p.path.is_ident("str")
            } else {
                false
            }
        }
        Type::Path(p) => p.path.is_ident("String"),
        _ => false,
    };

    let name_str_ident = format_ident!("{}_str", name_ident);
    let get_param_code = quote ! {
        match #arg_map.get(#name_str) {
            Some(v) => v,
            None => return #aqua::server::ServerRouteError::FailedHnalde(
                format!("No exit params '{}'", #name_str)
            ).into_response(),
        }
    };

    if is_str {
        quote! {
            let #name_str_ident = #get_param_code;
            let #name_ident: #ty = #name_str_ident.as_str().into();
        }
    } else {
        quote! {
            let #name_str_ident = #get_param_code;
            let #name_ident = match <#ty as std::str::FromStr>::from_str(#name_str_ident) {
                Ok(v) => v,
                Err(err) => return #aqua::server::ServerRouteError::InvalidURIArgument(
                    format!("convert argument '{}' failed for: {}", #name_str, err)
                ).into_response(),
            };
        }
    }
}

fn extract_state_inner_type(ty: &Type) -> Option<&Type> {
    if let Type::Path(type_path) = ty {
        if type_path.qself.is_none() && type_path.path.segments.len() == 1 {
            let segment = &type_path.path.segments[0];

            if segment.ident == "HttpState" {
                if let PathArguments::AngleBracketed(angle_bracketed) = &segment.arguments {
                    if angle_bracketed.args.len() == 1 {
                        if let GenericArgument::Type(inner_ty) = &angle_bracketed.args[0] {
                            return Some(inner_ty);
                        }
                    }
                }
            }
        }
    }
    None
}